// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// clang-format off
// #import 'chrome://os-settings/chromeos/os_settings.js';

// #import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
// #import {assertEquals, assertFalse, assertNotEquals, assertTrue} from '../../chai_assert.js';
// #import {flush} from'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
// #import {OpenWindowProxyImpl, Router, Route, routes} from 'chrome://os-settings/chromeos/os_settings.js';
// #import {eventToPromise} from 'chrome://test/test_util.js';
// #import {FakeUserActionRecorder} from './fake_user_action_recorder.m.js';
// #import {FakeSettingsSearchHandler} from './fake_settings_search_handler.m.js';
// #import {setSearchHandlerForTesting, setUserActionRecorderForTesting} from 'chrome://os-settings/chromeos/os_settings.js';
// #import {TestBrowserProxy} from '../../test_browser_proxy.js';
// clang-format on

/** @fileoverview Runs tests for the OS settings search box. */

/**
 * @implements {settings.OpenWindowProxy}
 */
class TestOpenWindowProxy extends TestBrowserProxy {
  constructor() {
    super([
      'openURL',
    ]);
  }

  /** @override */
  openURL(url) {
    this.methodCalled('openURL', url);
  }
}

suite('OSSettingsSearchBox', () => {
  // TODO(hsuregan): Keep count and add getters for metrics.
  class MockMetricsPrivate {
    recordEnumerationValue(metricName, value, enumSize) {}

    recordSparseValue(metricName, value) {}

    recordTime(metricName, value) {}

    // Required to use recordSparsValue()
    recordSparseHashable(metricName, value) {}
  }

  /** @const {number} */
  const DEFAULT_RELEVANCE_SCORE = 0.5;

  /** @const {!Array<mojoBase.mojom.String16>} */
  const DEFAULT_PAGE_HIERARCHY = [];

  /** @type {?OsToolbar} */
  let toolbar;

  /** @type {?OsSettingsSearchBox} */
  let searchBox;

  /** @type {?CrSearchFieldElement} */
  let field;

  /** @type {?IronDropdownElement} */
  let dropDown;

  /** @type {?IronListElement} */
  let resultList;

  /** @type {*} */
  let settingsSearchHandler;

  /** @type {?chromeos.settings.mojom.UserActionRecorderInterface} */
  let userActionRecorder;

  /** @type {?HTMLElement} */
  let noResultsSection;

  /** @type {?TestOpenWindowProxy} */
  let openWindowProxy = null;

  function isTextSelected() {
    const input = field.$.searchInput;
    return input.selectionStart === 0 &&
        input.selectionEnd === input.value.length;
  }

  /** @param {string} term */
  async function simulateSearch(term) {
    field.$.searchInput.value = term;
    field.onSearchTermInput();
    field.onSearchTermSearch();
    await settingsSearchHandler.search;
    Polymer.dom.flush();
  }

  async function waitForListUpdate() {
    // Wait for iron-list to complete resizing.
    await test_util.eventToPromise('iron-resize', resultList);
    Polymer.dom.flush();
  }

  async function waitForResultsFetched() {
    // Wait for search results to be fetched.
    await test_util.eventToPromise('search-results-fetched', searchBox);
    Polymer.dom.flush();
  }

  /**
   * @param {string} resultText Exact string of the result to be displayed.
   * @param {string} path Url path with optional params.
   * @param {?chromeos.settings.mojom.SearchResultIcon} icon Result icon enum.
   * @param {?Boolean} wasGeneratedFromTextMatch If result was generated by
   *     text match, defaults to true.
   * @return {!chromeos.settings.mojom.SearchResult} A search result.
   */
  function fakeResult(
      resultText, urlPathWithParameters, icon, wasGeneratedFromTextMatch) {
    return /** @type {!mojom.SearchResult} */ ({
      resultText: {
        data: Array.from(resultText, c => c.charCodeAt()),
      },
      urlPathWithParameters: urlPathWithParameters,
      icon: icon ? icon : chromeos.settings.mojom.SearchResultIcon.MIN_VALUE,
      wasGeneratedFromTextMatch: wasGeneratedFromTextMatch === undefined ?
          true :
          wasGeneratedFromTextMatch,
      id: {
        section: chromeos.settings.mojom.Section.MIN_VALUE,
        subpage: chromeos.settings.mojom.Subpage.MIN_VALUE,
        setting: chromeos.settings.mojom.Setting.MIN_VALUE,
      },
      type: chromeos.settings.mojom.SearchResultType.MIN_VALUE,
      relevanceScore: DEFAULT_RELEVANCE_SCORE,
      settingsPageHierarchy: DEFAULT_PAGE_HIERARCHY,
    });
  }

  function setupSearchBox() {
    chrome.metricsPrivate = new MockMetricsPrivate();
    settingsSearchHandler = new settings.FakeSettingsSearchHandler();
    settings.setSearchHandlerForTesting(settingsSearchHandler);

    userActionRecorder = new settings.FakeUserActionRecorder();
    settings.setUserActionRecorderForTesting(userActionRecorder);

    toolbar = document.createElement('os-toolbar');
    assertTrue(!!toolbar);
    document.body.appendChild(toolbar);
    Polymer.dom.flush();

    searchBox = toolbar.$$('os-settings-search-box');
    assertTrue(!!searchBox);
    field = searchBox.$$('cr-toolbar-search-field');
    assertTrue(!!field);
    dropDown = searchBox.$$('iron-dropdown');
    assertTrue(!!dropDown);
    resultList = searchBox.$$('iron-list');
    assertTrue(!!resultList);
    noResultsSection = searchBox.$$('#noSearchResultsContainer');
    assertTrue(!!noResultsSection);
  }

  setup(function() {
    setupSearchBox();
    settings.Router.getInstance().navigateTo(settings.routes.BASIC);

    openWindowProxy = new TestOpenWindowProxy();
    OpenWindowProxyImpl.setInstance(openWindowProxy);
  });

  teardown(async () => {
    // Clear search field for next test.
    await simulateSearch('');
    settings.setUserActionRecorderForTesting(null);
    settings.setSearchHandlerForTesting(null);
  });

  test('Search availability changed', async () => {
    settingsSearchHandler.setFakeResults([fakeResult('result')]);
    await simulateSearch('test query');
    assertTrue(dropDown.opened);
    assertEquals(searchBox.searchResults_.length, 1);

    settingsSearchHandler.setFakeResults([fakeResult('1'), fakeResult('2')]);
    assertTrue(dropDown.opened);
    assertEquals(searchBox.searchResults_.length, 1);

    // Check that the list updates when the dropdown is open, and the dropdown
    // remains open.
    settingsSearchHandler.simulateSearchResultAvailabilityChanged();
    await waitForResultsFetched();
    assertTrue(dropDown.opened);
    assertEquals(searchBox.searchResults_.length, 2);

    // User clicks outside the search box, closing the dropdown.
    searchBox.blur();
    assertFalse(dropDown.opened);

    settingsSearchHandler.setFakeResults([fakeResult('result')]);
    assertFalse(dropDown.opened);
    assertEquals(searchBox.searchResults_.length, 2);

    // Check that the list updates when the dropdown is closed, and the dropdown
    // remains closed.
    settingsSearchHandler.simulateSearchResultAvailabilityChanged();
    await waitForResultsFetched();
    assertFalse(dropDown.opened);
    assertEquals(searchBox.searchResults_.length, 1);

    // The first item should be selected immediately when the search results
    // change even if the change occurred while the dropdown was closed.
    field.$.searchInput.focus();
    await waitForListUpdate();
    assertTrue(dropDown.opened);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().searchResult.resultText,
        searchBox.selectedItem_.resultText);
  });

  test('User action search event', async () => {
    settingsSearchHandler.setFakeResults([]);

    assertEquals(userActionRecorder.searchCount, 0);
    await simulateSearch('query');
    assertEquals(userActionRecorder.searchCount, 1);
  });

  test(
      'Clicking magnifying glass shows dropdown and selects all text',
      async () => {
        settingsSearchHandler.setFakeResults([fakeResult('a')]);
        await simulateSearch('query');
        await waitForListUpdate();
        assertTrue(dropDown.opened);
        searchBox.blur();

        assertFalse(dropDown.opened);
        assertFalse(isTextSelected());

        field.$.icon.click();
        assertTrue(isTextSelected());
        assertTrue(dropDown.opened);
      });

  test('Dropdown opens correctly when results are fetched', async () => {
    // Show no results in dropdown if no results are returned.
    settingsSearchHandler.setFakeResults([]);
    assertFalse(dropDown.opened);
    await simulateSearch('query 1');
    assertTrue(dropDown.opened);
    assertEquals(searchBox.searchResults_.length, 0);
    assertFalse(noResultsSection.hidden);

    assertEquals(userActionRecorder.searchCount, 1);

    // Show result list if results are returned, and hide no results div.
    settingsSearchHandler.setFakeResults([fakeResult('result')]);
    await simulateSearch('query 2');
    assertNotEquals(searchBox.searchResults_.length, 0);
    assertTrue(noResultsSection.hidden);
  });

  test('Restore previous existing search results', async () => {
    settingsSearchHandler.setFakeResults([fakeResult('result 1')]);
    await simulateSearch('query');
    assertTrue(dropDown.opened);
    const resultRow = resultList.items[0];

    // Child blur elements except field should not trigger closing of dropdown.
    resultList.blur();
    assertTrue(dropDown.opened);
    dropDown.blur();
    assertTrue(dropDown.opened);

    // User clicks outside the search box, closing the dropdown.
    searchBox.blur();
    assertFalse(dropDown.opened);

    // User clicks on input, restoring old results and opening dropdown.
    field.$.searchInput.focus();
    assertEquals('query', field.$.searchInput.value);
    assertTrue(dropDown.opened);

    // The same result row exists.
    assertEquals(resultRow, resultList.items[0]);

    // Search field is blurred, closing the dropdown.
    field.$.searchInput.blur();
    assertFalse(dropDown.opened);

    // User clicks on input, restoring old results and opening dropdown.
    field.$.searchInput.focus();
    assertEquals('query', field.$.searchInput.value);
    assertTrue(dropDown.opened);

    // The same result row exists.
    assertEquals(resultRow, resultList.items[0]);
  });

  test('Search result rows are selected correctly', async () => {
    settingsSearchHandler.setFakeResults([fakeResult('a'), fakeResult('b')]);
    await simulateSearch('query');
    await waitForListUpdate();

    assertTrue(dropDown.opened);
    assertEquals(resultList.items.length, 2);

    // The first row should be selected when results are fetched.
    assertEquals(resultList.selectedItem, resultList.items[0]);

    // Test ArrowUp and ArrowDown interaction with selecting.
    const arrowUpEvent = new KeyboardEvent(
        'keydown', {cancelable: true, key: 'ArrowUp', keyCode: 38});
    const arrowDownEvent = new KeyboardEvent(
        'keydown', {cancelable: true, key: 'ArrowDown', keyCode: 40});

    // ArrowDown event should select next row.
    searchBox.dispatchEvent(arrowDownEvent);
    assertEquals(resultList.selectedItem, resultList.items[1]);

    // If last row selected, ArrowDown brings select back to first row.
    searchBox.dispatchEvent(arrowDownEvent);
    assertEquals(resultList.selectedItem, resultList.items[0]);

    // If first row selected, ArrowUp brings select back to last row.
    searchBox.dispatchEvent(arrowUpEvent);
    assertEquals(resultList.selectedItem, resultList.items[1]);

    // ArrowUp should bring select previous row.
    searchBox.dispatchEvent(arrowUpEvent);
    assertEquals(resultList.selectedItem, resultList.items[0]);

    // Test that ArrowLeft and ArrowRight do nothing.
    const arrowLeftEvent = new KeyboardEvent(
        'keydown', {cancelable: true, key: 'ArrowLeft', keyCode: 37});
    const arrowRightEvent = new KeyboardEvent(
        'keydown', {cancelable: true, key: 'ArrowRight', keyCode: 39});

    // No change on ArrowLeft
    searchBox.dispatchEvent(arrowLeftEvent);
    assertEquals(resultList.selectedItem, resultList.items[0]);

    // No change on ArrowRight
    searchBox.dispatchEvent(arrowRightEvent);
    assertEquals(resultList.selectedItem, resultList.items[0]);
  });

  test('Keydown Enter on search box can cause route change', async () => {
    settingsSearchHandler.setFakeResults(
        [fakeResult('WiFi Settings', 'networks?type=WiFi')]);
    await simulateSearch('fake query');
    await waitForListUpdate();

    const enterEvent = new KeyboardEvent(
        'keydown', {cancelable: true, key: 'Enter', keyCode: 13});

    // Keydown with Enter key on the searchBox causes navigation to selected
    // row's route.
    searchBox.dispatchEvent(enterEvent);
    Polymer.dom.flush();
    assertFalse(dropDown.opened);
    const router = settings.Router.getInstance();
    assertEquals(router.getQueryParameters().get('search'), 'fake query');
    assertEquals(router.getCurrentRoute().path, '/networks');
    assertEquals(router.getQueryParameters().get('type'), 'WiFi');
  });

  test(
      'Keypress Enter on personalization hub result can open a new window',
      async () => {
        // Enable personalization hub feature.
        loadTimeData.overrideValues({isPersonalizationHubEnabled: true});
        assertTrue(loadTimeData.getBoolean('isPersonalizationHubEnabled'));

        const result = fakeResult('Wallpaper', 'personalization?settingId=500');
        result.id.setting = chromeos.settings.mojom.Setting.kOpenWallpaper;

        settingsSearchHandler.setFakeResults([result]);
        await simulateSearch('fake query 1');
        await waitForListUpdate();

        const selectedOsRow = searchBox.getSelectedOsSearchResultRow_();
        assertTrue(!!selectedOsRow);
        assertEquals('cr:open-in-new', selectedOsRow.getActionTypeIcon_());

        // Keypress with Enter key on any row specifically causes navigation to
        // selected row's route. This can't happen unless the row is focused.
        const enterEvent = new KeyboardEvent(
            'keypress', {cancelable: true, key: 'Enter', keyCode: 13});
        selectedOsRow.$.searchResultContainer.dispatchEvent(enterEvent);

        assertEquals(1, openWindowProxy.getCallCount('openURL'));
      });

  test(
      'Clicking on personalization hub result can open a new window',
      async () => {
        // Enable personalization hub feature.
        loadTimeData.overrideValues({isPersonalizationHubEnabled: true});
        assertTrue(loadTimeData.getBoolean('isPersonalizationHubEnabled'));

        const result = fakeResult('Wallpaper', 'personalization?settingId=500');
        result.id.setting = chromeos.settings.mojom.Setting.kOpenWallpaper;

        settingsSearchHandler.setFakeResults([result]);
        await simulateSearch('fake query 1');
        await waitForListUpdate();

        const selectedOsRow = searchBox.getSelectedOsSearchResultRow_();
        assertTrue(!!selectedOsRow);
        assertEquals('cr:open-in-new', selectedOsRow.getActionTypeIcon_());

        // Clicking on the searchResultContainer of the row opens a new window.
        selectedOsRow.$.searchResultContainer.click();

        assertEquals(1, openWindowProxy.getCallCount('openURL'));
      });

  test(
      'Clicking on personalization hub result causes route change' +
          ' if personalization hub feature is disabled',
      async () => {
        // Disable personalization hub feature.
        loadTimeData.overrideValues({isPersonalizationHubEnabled: false});
        assertFalse(loadTimeData.getBoolean('isPersonalizationHubEnabled'));

        const result = fakeResult('Wallpaper', 'personalization?settingId=500');
        result.id.setting = chromeos.settings.mojom.Setting.kOpenWallpaper;

        settingsSearchHandler.setFakeResults([result]);
        await simulateSearch('fake query 2');
        await waitForListUpdate();

        const selectedOsRow = searchBox.getSelectedOsSearchResultRow_();
        assertTrue(!!selectedOsRow);
        assertEquals('cr:arrow-forward', selectedOsRow.getActionTypeIcon_());

        // Clicking on the searchResultContainer of the row correctly changes
        // the route and dropdown to close.
        selectedOsRow.$.searchResultContainer.click();
        assertFalse(dropDown.opened);
        assertEquals(0, openWindowProxy.getCallCount('openURL'));
        const router = settings.Router.getInstance();
        assertEquals(router.getQueryParameters().get('search'), 'fake query 2');
        assertEquals(router.getCurrentRoute().path, '/personalization');
        assertEquals(router.getQueryParameters().get('settingId'), '500');
      });

  test('Keypress Enter on row causes route change', async () => {
    settingsSearchHandler.setFakeResults(
        [fakeResult('WiFi Settings', 'networks?type=WiFi')]);
    await simulateSearch('fake query 1');
    await waitForListUpdate();

    const selectedOsRow = searchBox.getSelectedOsSearchResultRow_();
    assertTrue(!!selectedOsRow);

    // Keypress with Enter key on any row specifically causes navigation to
    // selected row's route. This can't happen unless the row is focused.
    const enterEvent = new KeyboardEvent(
        'keypress', {cancelable: true, key: 'Enter', keyCode: 13});
    selectedOsRow.$.searchResultContainer.dispatchEvent(enterEvent);
    assertFalse(dropDown.opened);
    const router = settings.Router.getInstance();
    assertEquals(router.getQueryParameters().get('search'), 'fake query 1');
    assertEquals(router.getCurrentRoute().path, '/networks');
    assertEquals(router.getQueryParameters().get('type'), 'WiFi');
  });

  test('Route change when result row is clicked', async () => {
    settingsSearchHandler.setFakeResults(
        [fakeResult('WiFi Settings', 'networks?type=WiFi')]);
    await simulateSearch('fake query 2');
    await waitForListUpdate();

    const searchResultRow = searchBox.getSelectedOsSearchResultRow_();

    // Clicking on the searchResultContainer of the row correctly changes the
    // route and dropdown to close.
    searchResultRow.$.searchResultContainer.click();

    assertFalse(dropDown.opened);
    const router = settings.Router.getInstance();
    assertEquals(router.getQueryParameters().get('search'), 'fake query 2');
    assertEquals(router.getCurrentRoute().path, '/networks');
    assertEquals(router.getQueryParameters().get('type'), 'WiFi');
  });

  test('Selecting result a second time does not deselect it.', async () => {
    settingsSearchHandler.setFakeResults(
        [fakeResult('WiFi Settings', 'networks?type=WiFi')]);
    await simulateSearch('query');
    await waitForListUpdate();

    // Clicking a selected item does not deselect it.
    const searchResultRow = searchBox.getSelectedOsSearchResultRow_();
    searchResultRow.$.searchResultContainer.click();
    assertEquals(resultList.selectedItem, resultList.items[0]);
    assertFalse(dropDown.opened);

    // Open search drop down again.
    field.$.searchInput.focus();
    assertTrue(dropDown.opened);

    // Clicking again does not deslect the row.
    searchResultRow.$.searchResultContainer.click();
    assertEquals(resultList.selectedItem, resultList.items[0]);
  });

  test('Test no bolding if not generated from text match', async () => {
    settingsSearchHandler.setFakeResults([fakeResult(
        'Search and Assistant', undefined, undefined,
        /*wasGeneratedFromTextMatch=*/false)]);
    await simulateSearch(`Search`);
    await waitForListUpdate();
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Search and Assistant`);
  });

  test('Tokenize and match result text to query text', async () => {
    settingsSearchHandler.setFakeResults([fakeResult('Search and Assistant')]);
    await simulateSearch(`Assistant Search`);
    await waitForListUpdate();
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `<b>Search</b> and <b>Assistant</b>`);
  });

  test('Bold result text to matching query', async () => {
    settingsSearchHandler.setFakeResults([fakeResult('Search and Assistant')]);
    await simulateSearch(`a`);
    await waitForListUpdate();
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Se<b>a</b>rch <b>a</b>nd <b>A</b>ssist<b>a</b>nt`);
  });

  test('Bold result including ignored characters', async () => {
    settingsSearchHandler.setFakeResults([fakeResult('Turn on Wi-Fi')]);
    await simulateSearch(`wif`);
    await waitForListUpdate();
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Turn on <b>Wi-F</b>i`);
    await simulateSearch(`wi f`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Turn on <b>Wi-F</b>i`);
    await simulateSearch(`wi-f`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Turn on <b>Wi-F</b>i`);

    settingsSearchHandler.setFakeResults([fakeResult('Touchpad tap-to-click')]);
    await simulateSearch(`tap to cli`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Touchpad <b>tap-to-cli</b>ck`);

    await simulateSearch(`taptocli`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Touchpad <b>tap-to-cli</b>ck`);
    await simulateSearch(`tap-to-cli`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Touchpad <b>tap-to-cli</b>ck`);

    settingsSearchHandler.setFakeResults([fakeResult('Touchpad tap-to-click')]);
    await simulateSearch(`tap top cli`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Touchpad <b>tap-to-cli</b>ck`);

    settingsSearchHandler.setFakeResults([fakeResult('wxyz Tap-To-Click')]);
    await simulateSearch(`tap toxy cli`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `w<b>xy</b>z <b>Tap-To</b>-Click`);

    settingsSearchHandler.setFakeResults(
        [fakeResult('Tap-to-click Tips Title')]);
    await simulateSearch(`tap ti`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `<b>Tap</b>-to-click <b>Ti</b>ps <b>Ti</b>tle`);
  });

  test('Test query longer than result blocks', async () => {
    settingsSearchHandler.setFakeResults([fakeResult('Turn on Wi-Fi')]);
    await simulateSearch(`onwifi`);
    await waitForListUpdate();
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Turn <b>on</b> <b>Wi-Fi</b>`);
  });

  test('Test bolding of accented characters', async () => {
    settingsSearchHandler.setFakeResults([fakeResult('Crème Brûlée')]);
    await simulateSearch(`E U`);
    await waitForListUpdate();
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Cr<b>è</b>me Br<b>û</b>l<b>é</b>e`);
  });

  test('Test no spaces nor characters that have upper/lower case', async () => {
    settingsSearchHandler.setFakeResults([fakeResult('キーボード設定---')]);
    await simulateSearch(`キー設`);
    await waitForListUpdate();
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `<b>キ</b><b>ー</b>ボ<b>ー</b>ド<b>設</b>定---`);
  });

  test('Test blankspace types in result maintained', async () => {
    const resultText = 'Turn\xa0on  \xa0Wi-Fi ';

    settingsSearchHandler.setFakeResults([fakeResult(resultText)]);
    await simulateSearch(`wif`);
    await waitForListUpdate();
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `Turn&nbsp;on  &nbsp;<b>Wi-F</b>i `);
  });

  test('Test longest common substring for mispellings', async () => {
    settingsSearchHandler.setFakeResults([fakeResult('Linux')]);
    await simulateSearch(`Linuux`);
    await waitForListUpdate();
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `<b>Linu</b>x`);

    settingsSearchHandler.setFakeResults([fakeResult('Linux')]);
    await simulateSearch(`Llinuc`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `<b>Linu</b>x`);

    settingsSearchHandler.setFakeResults([fakeResult('Display')]);
    await simulateSearch(`Dispplay`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `<b>Disp</b>lay`);

    settingsSearchHandler.setFakeResults([fakeResult('ABCDEF GHIJK LMNO')]);
    await simulateSearch(`MCDEMMM LM EF CDEABCDEFADBCDABDCEF`);
    assertEquals(
        searchBox.getSelectedOsSearchResultRow_().$.resultText.innerHTML,
        `<b>ABCDEF</b> GHIJK <b>LM</b>NO`);
  });

  test('Focus search input behavior on attached', async () => {
    PolymerTest.clearBody();
    settings.Router.getInstance().navigateTo(settings.routes.BASIC);
    setupSearchBox();
    assertEquals(field.root.activeElement, field.$.searchInput);

    PolymerTest.clearBody();
    settings.Router.getInstance().navigateTo(settings.routes.KEYBOARD);
    assertEquals(field.root.activeElement, null);
  });
});
